<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    @import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500;700&display=swap");

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: #000000;
      min-height: 100vh;
      overflow: hidden;
      font-family: "Arial", sans-serif;
    }

    .controls {
      position: absolute;
      top: 20px;
      left: 20px;
      display: flex;
      gap: 10px;
      z-index: 100;
    }

    .control-btn {
      padding: 10px20px;
      background: rgba(255, 255, 255, 0.2);
      border: none;
      border-radius: 25px;
      color: white;
      font-weight: bold;
      cursor: pointer;
      backdrop-filter: blur(5px);
      transition: all 0.3s ease;
      font-size: 14px;
    }

    .control-btn:hover {
      background: rgba(255, 255, 255, 0.3);
      transform: translateY(-2px);
      box-shadow: 05px15pxrgba(0, 0, 0, 0.2);
    }

    .speed-indicator {
      position: absolute;
      top: 20px;
      right: 20px;
      color: white;
      font-size: 16px;
      background: rgba(0, 0, 0, 0.3);
      padding: 8px16px;
      border-radius: 20px;
      backdrop-filter: blur(5px);
      z-index: 100;
    }

    .info {
      position: absolute;
      bottom: 20px;
      left: 50%;
      transform: translateX(-50%);
      color: rgba(255, 255, 255, 0.9);
      text-align: center;
      font-size: 14px;
      background: rgba(0, 0, 0, 0.3);
      padding: 15px25px;
      border-radius: 20px;
      backdrop-filter: blur(5px);
      z-index: 100;
      line-height: 1.4;
    }

    .container {
      position: relative;
      width: 100vw;
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .card-stream {
      position: absolute;
      width: 100vw;
      height: 180px;
      display: flex;
      align-items: center;
      overflow: visible;
    }

    .card-line {
      display: flex;
      align-items: center;
      gap: 60px;
      white-space: nowrap;
      cursor: grab;
      user-select: none;
      will-change: transform;
    }

    .card-line:active {
      cursor: grabbing;
    }

    .card-line.dragging {
      cursor: grabbing;
    }

    .card-line.css-animated {
      animation: scrollCards 40s linear infinite;
    }

    @keyframes scrollCards {
      0% {
        transform: translateX(-100%);
      }

      100% {
        transform: translateX(100vw);
      }
    }

    .card-wrapper {
      position: relative;
      width: 400px;
      height: 250px;
      flex-shrink: 0;
    }

    .card {
      position: absolute;
      top: 0;
      left: 0;
      width: 400px;
      height: 250px;
      border-radius: 15px;
      overflow: hidden;
    }

    .card-normal {
      background: transparent;
      box-shadow: 015px40pxrgba(0, 0, 0, 0.4);
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      padding: 0;
      color: white;
      z-index: 2;
      position: relative;
      overflow: hidden;
    }

    .card-image {
      width: 100%;
      height: 100%;
      object-fit: cover;
      border-radius: 15px;
      transition: all 0.3s ease;
      filter: brightness(1.1) contrast(1.1);
      box-shadow: inset 0020pxrgba(0, 0, 0, 0.1);
    }

    .card-image:hover {
      filter: brightness(1.2) contrast(1.2);
    }

    .card-ascii {
      background: transparent;
      z-index: 1;
      position: absolute;
      top: 0;
      left: 0;
      width: 400px;
      height: 250px;
      border-radius: 15px;
      overflow: hidden;
    }

    .card-chip {
      width: 40px;
      height: 30px;
      background: linear-gradient(45deg, #ffd700, #ffed4e);
      border-radius: 5px;
      position: relative;
      margin-bottom: 20px;
    }

    .card-chip::before {
      content: "";
      position: absolute;
      top: 3px;
      left: 3px;
      right: 3px;
      bottom: 3px;
      background: linear-gradient(45deg, #e6c200, #f4d03f);
      border-radius: 2px;
    }

    .contactless {
      position: absolute;
      top: 60px;
      left: 20px;
      width: 25px;
      height: 25px;
      border: 2px solid rgba(255, 255, 255, 0.8);
      border-radius: 50%;
      background: radial-gradient(circle, rgba(255, 255, 255, 0.2), transparent);
    }

    .contactless::after {
      content: "";
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 15px;
      height: 15px;
      border: 1px solid rgba(255, 255, 255, 0.6);
      border-radius: 50%;
    }

    .card-number {
      font-size: 22px;
      font-weight: bold;
      letter-spacing: 3px;
      margin-bottom: 15px;
      text-shadow: 02px4pxrgba(0, 0, 0, 0.3);
    }

    .card-info {
      display: flex;
      justify-content: space-between;
      align-items: flex-end;
    }

    .card-holder {
      color: white;
      font-size: 14px;
      text-transform: uppercase;
    }

    .card-expiry {
      color: white;
      font-size: 14px;
    }

    .card-logo {
      position: absolute;
      top: 20px;
      right: 20px;
      font-size: 18px;
      font-weight: bold;
      color: white;
      text-shadow: 02px4pxrgba(0, 0, 0, 0.3);
    }

    .ascii-content {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      color: rgba(220, 210, 255, 0.6);
      font-family: "Courier New", monospace;
      font-size: 11px;
      line-height: 13px;
      overflow: hidden;
      white-space: pre;
      clip-path: inset(0 calc(100% - var(--clip-left, 0%)) 00);
      animation: glitch 0.1s infinite linear alternate-reverse;
      margin: 0;
      padding: 0;
      text-align: left;
      vertical-align: top;
      box-sizing: border-box;
      -webkit-mask-image: linear-gradient(to right,
          rgba(0, 0, 0, 1) 0%,
          rgba(0, 0, 0, 0.8) 30%,
          rgba(0, 0, 0, 0.6) 50%,
          rgba(0, 0, 0, 0.4) 80%,
          rgba(0, 0, 0, 0.2) 100%);
      mask-image: linear-gradient(to right,
          rgba(0, 0, 0, 1) 0%,
          rgba(0, 0, 0, 0.8) 30%,
          rgba(0, 0, 0, 0.6) 50%,
          rgba(0, 0, 0, 0.4) 80%,
          rgba(0, 0, 0, 0.2) 100%);
    }

    @keyframes glitch {
      0% {
        opacity: 1;
      }

      15% {
        opacity: 0.9;
      }

      16% {
        opacity: 1;
      }

      49% {
        opacity: 0.8;
      }

      50% {
        opacity: 1;
      }

      99% {
        opacity: 0.9;
      }

      100% {
        opacity: 1;
      }
    }

    .scanner {
      display: none;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 4px;
      height: 300px;
      border-radius: 30px;
      background: linear-gradient(to bottom,
          transparent,
          rgba(0, 255, 255, 0.8),
          rgba(0, 255, 255, 1),
          rgba(0, 255, 255, 0.8),
          transparent);
      box-shadow: 0020pxrgba(0, 255, 255, 0.8), 0040pxrgba(0, 255, 255, 0.4);
      animation: scanPulse 2s ease-in-out infinite alternate;
      z-index: 10;
    }

    @keyframes scanPulse {
      0% {
        opacity: 0.8;
        transform: translate(-50%, -50%) scaleY(1);
      }

      100% {
        opacity: 1;
        transform: translate(-50%, -50%) scaleY(1.1);
      }
    }

    .scanner-label {
      position: absolute;
      bottom: -40px;
      left: 50%;
      transform: translateX(-50%);
      color: rgba(0, 255, 255, 0.9);
      font-size: 12px;
      font-weight: bold;
      text-transform: uppercase;
      letter-spacing: 2px;
      text-shadow: 0010pxrgba(0, 255, 255, 0.5);
    }

    .card-normal {
      clip-path: inset(000 var(--clip-right, 0%));
    }

    .card-ascii {
      clip-path: inset(0 calc(100% - var(--clip-left, 0%)) 00);
    }

    .scan-effect {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: linear-gradient(90deg,
          transparent,
          rgba(0, 255, 255, 0.4),
          transparent);
      animation: scanEffect 0.6s ease-out;
      pointer-events: none;
      z-index: 5;
    }

    @keyframes scanEffect {
      0% {
        transform: translateX(-100%);
        opacity: 0;
      }

      50% {
        opacity: 1;
      }

      100% {
        transform: translateX(100%);
        opacity: 0;
      }
    }

    .instructions {
      position: absolute;
      top: 50%;
      right: 30px;
      transform: translateY(-50%);
      color: rgba(255, 255, 255, 0.7);
      font-size: 14px;
      max-width: 200px;
      text-align: right;
      z-index: 5;
    }

    #particleCanvas {
      position: absolute;
      top: 50%;
      left: 0;
      transform: translateY(-50%);
      width: 100vw;
      height: 250px;
      z-index: 0;
      pointer-events: none;
    }

    #scannerCanvas {
      position: absolute;
      top: 50%;
      left: -3px;
      transform: translateY(-50%);
      width: 100vw;
      height: 300px;
      z-index: 15;
      pointer-events: none;
    }
  </style>
</head>

<body>
  <div class="controls">
    <button onclick="toggleAnimation()" class="control-btn">⏸️ Pause</button>
    <button onclick="resetPosition()" class="control-btn">🔄 Reset</button>
    <button onclick="changeDirection()" class="control-btn">↔️ Direction</button>
  </div>

  <div class="speed-indicator">Speed: <span id="speedValue">120</span> px/s</div>

  <div class="container">
    <canvas id="particleCanvas"></canvas>
    <canvas id="scannerCanvas"></canvas>
    <div class="scanner"></div>
    <div class="card-stream" id="cardStream">
      <div class="card-line" id="cardLine"></div>
    </div>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
  <script>
    const codeChars =
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(){}[]<>;:,._-+=!@#$%^&*|\\/\"'`~?";

    const scannerLeft = window.innerWidth / 2 - 2;
    const scannerRight = window.innerWidth / 2 + 2;

    class CardStreamController {
      constructor() {
        this.container = document.getElementById("cardStream");
        this.cardLine = document.getElementById("cardLine");
        this.speedIndicator = document.getElementById("speedValue");

        this.position = 0;
        this.velocity = 120;
        this.direction = -1;
        this.isAnimating = true;
        this.isDragging = false;

        this.lastTime = 0;
        this.lastMouseX = 0;
        this.mouseVelocity = 0;
        this.friction = 0.95;
        this.minVelocity = 30;

        this.containerWidth = 0;
        this.cardLineWidth = 0;

        this.init();
      }

      init() {
        this.populateCardLine();
        this.calculateDimensions();
        this.setupEventListeners();
        this.updateCardPosition();
        this.animate();
        this.startPeriodicUpdates();
      }

      calculateDimensions() {
        this.containerWidth = this.container.offsetWidth;
        const cardWidth = 400;
        const cardGap = 60;
        const cardCount = this.cardLine.children.length;
        this.cardLineWidth = (cardWidth + cardGap) * cardCount;
      }

      setupEventListeners() {
        this.cardLine.addEventListener("mousedown", (e) => this.startDrag(e));
        document.addEventListener("mousemove", (e) => this.onDrag(e));
        document.addEventListener("mouseup", () => this.endDrag());

        this.cardLine.addEventListener(
          "touchstart",
          (e) => this.startDrag(e.touches[0]),
          { passive: false }
        );
        document.addEventListener("touchmove", (e) => this.onDrag(e.touches[0]), {
          passive: false,
        });
        document.addEventListener("touchend", () => this.endDrag());

        this.cardLine.addEventListener("wheel", (e) => this.onWheel(e));
        this.cardLine.addEventListener("selectstart", (e) => e.preventDefault());
        this.cardLine.addEventListener("dragstart", (e) => e.preventDefault());

        window.addEventListener("resize", () => this.calculateDimensions());
      }

      startDrag(e) {
        e.preventDefault();

        this.isDragging = true;
        this.isAnimating = false;
        this.lastMouseX = e.clientX;
        this.mouseVelocity = 0;

        const transform = window.getComputedStyle(this.cardLine).transform;
        if (transform !== "none") {
          const matrix = new DOMMatrix(transform);
          this.position = matrix.m41;
        }

        this.cardLine.style.animation = "none";
        this.cardLine.classList.add("dragging");

        document.body.style.userSelect = "none";
        document.body.style.cursor = "grabbing";
      }

      onDrag(e) {
        if (!this.isDragging) return;
        e.preventDefault();

        const deltaX = e.clientX - this.lastMouseX;
        this.position += deltaX;
        this.mouseVelocity = deltaX * 60;
        this.lastMouseX = e.clientX;

        this.cardLine.style.transform = `translateX(${this.position}px)`;
        this.updateCardClipping();
      }

      endDrag() {
        if (!this.isDragging) return;

        this.isDragging = false;
        this.cardLine.classList.remove("dragging");

        if (Math.abs(this.mouseVelocity) > this.minVelocity) {
          this.velocity = Math.abs(this.mouseVelocity);
          this.direction = this.mouseVelocity > 0 ? 1 : -1;
        } else {
          this.velocity = 120;
        }

        this.isAnimating = true;
        this.updateSpeedIndicator();

        document.body.style.userSelect = "";
        document.body.style.cursor = "";
      }

      animate() {
        const currentTime = performance.now();
        const deltaTime = (currentTime - this.lastTime) / 1000;
        this.lastTime = currentTime;

        if (this.isAnimating && !this.isDragging) {
          if (this.velocity > this.minVelocity) {
            this.velocity *= this.friction;
          } else {
            this.velocity = Math.max(this.minVelocity, this.velocity);
          }

          this.position += this.velocity * this.direction * deltaTime;
          this.updateCardPosition();
          this.updateSpeedIndicator();
        }

        requestAnimationFrame(() => this.animate());
      }

      updateCardPosition() {
        const containerWidth = this.containerWidth;
        const cardLineWidth = this.cardLineWidth;

        if (this.position < -cardLineWidth) {
          this.position = containerWidth;
        } else if (this.position > containerWidth) {
          this.position = -cardLineWidth;
        }

        this.cardLine.style.transform = `translateX(${this.position}px)`;
        this.updateCardClipping();
      }

      updateSpeedIndicator() {
        this.speedIndicator.textContent = Math.round(this.velocity);
      }

      toggleAnimation() {
        this.isAnimating = !this.isAnimating;
        const btn = document.querySelector(".control-btn");
        console.log(btn)
        btn.textContent = this.isAnimating ? "⏸️ Pause" : "▶️ Play";

        if (this.isAnimating) {
          this.cardLine.style.animation = "none";
        }
      }

      resetPosition() {
        this.position = this.containerWidth;
        this.velocity = 120;
        this.direction = -1;
        this.isAnimating = true;
        this.isDragging = false;

        this.cardLine.style.animation = "none";
        this.cardLine.style.transform = `translateX(${this.position}px)`;
        this.cardLine.classList.remove("dragging");

        this.updateSpeedIndicator();

        const btn = document.querySelector(".control-btn");
        btn.textContent = "⏸️ Pause";
      }

      changeDirection() {
        this.direction *= -1;
        this.updateSpeedIndicator();
      }

      onWheel(e) {
        e.preventDefault();

        const scrollSpeed = 20;
        const delta = e.deltaY > 0 ? scrollSpeed : -scrollSpeed;

        this.position += delta;
        this.updateCardPosition();
        this.updateCardClipping();
      }

      generateCode(width, height) {
        const randInt = (min, max) =>
          Math.floor(Math.random() * (max - min + 1)) + min;
        const pick = (arr) => arr[randInt(0, arr.length - 1)];

        const header = [
          "// compiled preview • scanner demo",
          "/* generated for visual effect – not executed */",
          "const SCAN_WIDTH = 8;",
          "const FADE_ZONE = 35;",
          "const MAX_PARTICLES = 2500;",
          "const TRANSITION = 0.05;",
        ];

        const helpers = [
          "function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }",
          "function lerp(a, b, t) { return a + (b - a) * t; }",
          "const now = () => performance.now();",
          "function rng(min, max) { return Math.random() * (max - min) + min; }",
        ];

        const particleBlock = (idx) => [
          `class Particle${idx} {`,
          "  constructor(x, y, vx, vy, r, a) {",
          "    this.x = x; this.y = y;",
          "    this.vx = vx; this.vy = vy;",
          "    this.r = r; this.a = a;",
          "  }",
          "  step(dt) { this.x += this.vx * dt; this.y += this.vy * dt; }",
          "}",
        ];

        const scannerBlock = [
          "const scanner = {",
          "  x: Math.floor(window.innerWidth / 2),",
          "  width: SCAN_WIDTH,",
          "  glow: 3.5,",
          "};",
          "",
          "function drawParticle(ctx, p) {",
          "  ctx.globalAlpha = clamp(p.a, 0, 1);",
          "  ctx.drawImage(gradient, p.x - p.r, p.y - p.r, p.r * 2, p.r * 2);",
          "}",
        ];

        const loopBlock = [
          "function tick(t) {",
          "  // requestAnimationFrame(tick);",
          "  const dt = 0.016;",
          "  // update & render",
          "}",
        ];

        const misc = [
          "const state = { intensity: 1.2, particles: MAX_PARTICLES };",
          "const bounds = { w: window.innerWidth, h: 300 };",
          "const gradient = document.createElement('canvas');",
          "const ctx = gradient.getContext('2d');",
          "ctx.globalCompositeOperation = 'lighter';",
          "// ascii overlay is masked with a 3-phase gradient",
        ];

        const library = [];
        header.forEach((l) => library.push(l));
        helpers.forEach((l) => library.push(l));
        for (let b = 0; b < 3; b++)
          particleBlock(b).forEach((l) => library.push(l));
        scannerBlock.forEach((l) => library.push(l));
        loopBlock.forEach((l) => library.push(l));
        misc.forEach((l) => library.push(l));

        for (let i = 0; i < 40; i++) {
          const n1 = randInt(1, 9);
          const n2 = randInt(10, 99);
          library.push(`const v${i} = (${n1} + ${n2}) * 0.${randInt(1, 9)};`);
        }
        for (let i = 0; i < 20; i++) {
          library.push(
            `if (state.intensity > ${1 + (i % 3)}) { scanner.glow += 0.01; }`
          );
        }

        let flow = library.join(" ");
        flow = flow.replace(/\s+/g, " ").trim();
        const totalChars = width * height;
        while (flow.length < totalChars + width) {
          const extra = pick(library).replace(/\s+/g, " ").trim();
          flow += " " + extra;
        }

        let out = "";
        let offset = 0;
        for (let row = 0; row < height; row++) {
          let line = flow.slice(offset, offset + width);
          if (line.length < width) line = line + " ".repeat(width - line.length);
          out += line + (row < height - 1 ? "\n" : "");
          offset += width;
        }
        return out;
      }

      calculateCodeDimensions(cardWidth, cardHeight) {
        const fontSize = 11;
        const lineHeight = 13;
        const charWidth = 6;
        const width = Math.floor(cardWidth / charWidth);
        const height = Math.floor(cardHeight / lineHeight);
        return { width, height, fontSize, lineHeight };
      }

      createCardWrapper(index) {
        const wrapper = document.createElement("div");
        wrapper.className = "card-wrapper";

        const normalCard = document.createElement("div");
        normalCard.className = "card card-normal";

        const cardImages = [
          "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b55e654d1341fb06f8_4.1.png",
          "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b5a080a31ee7154b19_1.png",
          "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b5c1e4919fd69672b8_3.png",
          "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b5f6a5e232e7beb4be_2.png",
          "https://cdn.prod.website-files.com/68789c86c8bc802d61932544/689f20b5bea2f1b07392d936_4.png",
        ];

        const cardImage = document.createElement("img");
        cardImage.className = "card-image";
        cardImage.src = cardImages[index % cardImages.length];
        cardImage.alt = "Credit Card";

        cardImage.onerror = () => {
          const canvas = document.createElement("canvas");
          canvas.width = 400;
          canvas.height = 250;
          const ctx = canvas.getContext("2d");

          const gradient = ctx.createLinearGradient(0, 0, 400, 250);
          gradient.addColorStop(0, "#667eea");
          gradient.addColorStop(1, "#764ba2");

          ctx.fillStyle = gradient;
          ctx.fillRect(0, 0, 400, 250);

          cardImage.src = canvas.toDataURL();
        };

        normalCard.appendChild(cardImage);

        const asciiCard = document.createElement("div");
        asciiCard.className = "card card-ascii";

        const asciiContent = document.createElement("div");
        asciiContent.className = "ascii-content";

        const { width, height, fontSize, lineHeight } =
          this.calculateCodeDimensions(400, 250);
        asciiContent.style.fontSize = fontSize + "px";
        asciiContent.style.lineHeight = lineHeight + "px";
        asciiContent.textContent = this.generateCode(width, height);

        asciiCard.appendChild(asciiContent);
        wrapper.appendChild(normalCard);
        wrapper.appendChild(asciiCard);

        return wrapper;
      }

      updateCardClipping() {
        const scannerX = window.innerWidth / 2;
        const scannerWidth = 8;
        const scannerLeft = scannerX - scannerWidth / 2;
        const scannerRight = scannerX + scannerWidth / 2;
        let anyScanningActive = false;

        document.querySelectorAll(".card-wrapper").forEach((wrapper) => {
          const rect = wrapper.getBoundingClientRect();
          const cardLeft = rect.left;
          const cardRight = rect.right;
          const cardWidth = rect.width;

          const normalCard = wrapper.querySelector(".card-normal");
          const asciiCard = wrapper.querySelector(".card-ascii");

          if (cardLeft < scannerRight && cardRight > scannerLeft) {
            anyScanningActive = true;
            const scannerIntersectLeft = Math.max(scannerLeft - cardLeft, 0);
            const scannerIntersectRight = Math.min(
              scannerRight - cardLeft,
              cardWidth
            );

            const normalClipRight = (scannerIntersectLeft / cardWidth) * 100;
            const asciiClipLeft = (scannerIntersectRight / cardWidth) * 100;

            normalCard.style.setProperty("--clip-right", `${normalClipRight}%`);
            asciiCard.style.setProperty("--clip-left", `${asciiClipLeft}%`);

            if (!wrapper.hasAttribute("data-scanned") && scannerIntersectLeft > 0) {
              wrapper.setAttribute("data-scanned", "true");
              const scanEffect = document.createElement("div");
              scanEffect.className = "scan-effect";
              wrapper.appendChild(scanEffect);
              setTimeout(() => {
                if (scanEffect.parentNode) {
                  scanEffect.parentNode.removeChild(scanEffect);
                }
              }, 600);
            }
          } else {
            if (cardRight < scannerLeft) {
              normalCard.style.setProperty("--clip-right", "100%");
              asciiCard.style.setProperty("--clip-left", "100%");
            } else if (cardLeft > scannerRight) {
              normalCard.style.setProperty("--clip-right", "0%");
              asciiCard.style.setProperty("--clip-left", "0%");
            }
            wrapper.removeAttribute("data-scanned");
          }
        });

        if (window.setScannerScanning) {
          window.setScannerScanning(anyScanningActive);
        }
      }

      updateAsciiContent() {
        document.querySelectorAll(".ascii-content").forEach((content) => {
          if (Math.random() < 0.15) {
            const { width, height } = this.calculateCodeDimensions(400, 250);
            content.textContent = this.generateCode(width, height);
          }
        });
      }

      populateCardLine() {
        this.cardLine.innerHTML = "";
        const cardsCount = 30;
        for (let i = 0; i < cardsCount; i++) {
          const cardWrapper = this.createCardWrapper(i);
          this.cardLine.appendChild(cardWrapper);
        }
      }

      startPeriodicUpdates() {
        setInterval(() => {
          this.updateAsciiContent();
        }, 200);

        const updateClipping = () => {
          this.updateCardClipping();
          requestAnimationFrame(updateClipping);
        };
        updateClipping();
      }
    }

    let cardStream;

    function toggleAnimation() {
      if (cardStream) {
        cardStream.toggleAnimation();
      }
    }

    function resetPosition() {
      if (cardStream) {
        cardStream.resetPosition();
      }
    }

    function changeDirection() {
      if (cardStream) {
        cardStream.changeDirection();
      }
    }

    class ParticleSystem {
      constructor() {
        this.scene = null;
        this.camera = null;
        this.renderer = null;
        this.particles = null;
        this.particleCount = 400;
        this.canvas = document.getElementById("particleCanvas");

        this.init();
      }

      init() {
        this.scene = new THREE.Scene();

        this.camera = new THREE.OrthographicCamera(
          -window.innerWidth / 2,
          window.innerWidth / 2,
          125,
          -125,
          1,
          1000
        );
        this.camera.position.z = 100;

        this.renderer = new THREE.WebGLRenderer({
          canvas: this.canvas,
          alpha: true,
          antialias: true,
        });
        this.renderer.setSize(window.innerWidth, 250);
        this.renderer.setClearColor(0x000000, 0);

        this.createParticles();

        this.animate();

        window.addEventListener("resize", () => this.onWindowResize());
      }

      createParticles() {
        const geometry = new THREE.BufferGeometry();
        const positions = new Float32Array(this.particleCount * 3);
        const colors = new Float32Array(this.particleCount * 3);
        const sizes = new Float32Array(this.particleCount);
        const velocities = new Float32Array(this.particleCount);

        const canvas = document.createElement("canvas");
        canvas.width = 100;
        canvas.height = 100;
        const ctx = canvas.getContext("2d");

        const half = canvas.width / 2;
        const hue = 217;

        const gradient = ctx.createRadialGradient(half, half, 0, half, half, half);
        gradient.addColorStop(0.025, "#fff");
        gradient.addColorStop(0.1, `hsl(${hue}, 61%, 33%)`);
        gradient.addColorStop(0.25, `hsl(${hue}, 64%, 6%)`);
        gradient.addColorStop(1, "transparent");

        ctx.fillStyle = gradient;
        ctx.beginPath();
        ctx.arc(half, half, half, 0, Math.PI * 2);
        ctx.fill();

        const texture = new THREE.CanvasTexture(canvas);

        for (let i = 0; i < this.particleCount; i++) {
          positions[i * 3] = (Math.random() - 0.5) * window.innerWidth * 2;
          positions[i * 3 + 1] = (Math.random() - 0.5) * 250;
          positions[i * 3 + 2] = 0;

          colors[i * 3] = 1;
          colors[i * 3 + 1] = 1;
          colors[i * 3 + 2] = 1;

          const orbitRadius = Math.random() * 200 + 100;
          sizes[i] = (Math.random() * (orbitRadius - 60) + 60) / 8;

          velocities[i] = Math.random() * 60 + 30;
        }

        geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
        geometry.setAttribute("size", new THREE.BufferAttribute(sizes, 1));

        this.velocities = velocities;

        const alphas = new Float32Array(this.particleCount);
        for (let i = 0; i < this.particleCount; i++) {
          alphas[i] = (Math.random() * 8 + 2) / 10;
        }
        geometry.setAttribute("alpha", new THREE.BufferAttribute(alphas, 1));
        this.alphas = alphas;

        const material = new THREE.ShaderMaterial({
          uniforms: {
            pointTexture: { value: texture },
            size: { value: 15.0 },
          },
          vertexShader: `
        attribute float alpha;
        varying float vAlpha;
        varying vec3 vColor;
        uniform float size;
        
        void main() {
          vAlpha = alpha;
          vColor = color;
          vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
          gl_PointSize = size;
          gl_Position = projectionMatrix * mvPosition;
        }
      `,
          fragmentShader: `
        uniform sampler2D pointTexture;
        varying float vAlpha;
        varying vec3 vColor;
        
        void main() {
          gl_FragColor = vec4(vColor, vAlpha) * texture2D(pointTexture, gl_PointCoord);
        }
      `,
          transparent: true,
          blending: THREE.AdditiveBlending,
          depthWrite: false,
          vertexColors: true,
        });

        this.particles = new THREE.Points(geometry, material);
        this.scene.add(this.particles);
      }

      animate() {
        requestAnimationFrame(() => this.animate());

        if (this.particles) {
          const positions = this.particles.geometry.attributes.position.array;
          const alphas = this.particles.geometry.attributes.alpha.array;
          const time = Date.now() * 0.001;

          for (let i = 0; i < this.particleCount; i++) {
            positions[i * 3] += this.velocities[i] * 0.016;

            if (positions[i * 3] > window.innerWidth / 2 + 100) {
              positions[i * 3] = -window.innerWidth / 2 - 100;
              positions[i * 3 + 1] = (Math.random() - 0.5) * 250;
            }

            positions[i * 3 + 1] += Math.sin(time + i * 0.1) * 0.5;

            const twinkle = Math.floor(Math.random() * 10);
            if (twinkle === 1 && alphas[i] > 0) {
              alphas[i] -= 0.05;
            } else if (twinkle === 2 && alphas[i] < 1) {
              alphas[i] += 0.05;
            }

            alphas[i] = Math.max(0, Math.min(1, alphas[i]));
          }

          this.particles.geometry.attributes.position.needsUpdate = true;
          this.particles.geometry.attributes.alpha.needsUpdate = true;
        }

        this.renderer.render(this.scene, this.camera);
      }

      onWindowResize() {
        this.camera.left = -window.innerWidth / 2;
        this.camera.right = window.innerWidth / 2;
        this.camera.updateProjectionMatrix();

        this.renderer.setSize(window.innerWidth, 250);
      }

      destroy() {
        if (this.renderer) {
          this.renderer.dispose();
        }
        if (this.particles) {
          this.scene.remove(this.particles);
          this.particles.geometry.dispose();
          this.particles.material.dispose();
        }
      }
    }

    let particleSystem;

    class ParticleScanner {
      constructor() {
        this.canvas = document.getElementById("scannerCanvas");
        this.ctx = this.canvas.getContext("2d");
        this.animationId = null;

        this.w = window.innerWidth;
        this.h = 300;
        this.particles = [];
        this.count = 0;
        this.maxParticles = 800;
        this.intensity = 0.8;
        this.lightBarX = this.w / 2;
        this.lightBarWidth = 3;
        this.fadeZone = 60;

        this.scanTargetIntensity = 1.8;
        this.scanTargetParticles = 2500;
        this.scanTargetFadeZone = 35;

        this.scanningActive = false;

        this.baseIntensity = this.intensity;
        this.baseMaxParticles = this.maxParticles;
        this.baseFadeZone = this.fadeZone;

        this.currentIntensity = this.intensity;
        this.currentMaxParticles = this.maxParticles;
        this.currentFadeZone = this.fadeZone;
        this.transitionSpeed = 0.05;

        this.setupCanvas();
        this.createGradientCache();
        this.initParticles();
        this.animate();

        window.addEventListener("resize", () => this.onResize());
      }

      setupCanvas() {
        this.canvas.width = this.w;
        this.canvas.height = this.h;
        this.canvas.style.width = this.w + "px";
        this.canvas.style.height = this.h + "px";
        this.ctx.clearRect(0, 0, this.w, this.h);
      }

      onResize() {
        this.w = window.innerWidth;
        this.lightBarX = this.w / 2;
        this.setupCanvas();
      }

      createGradientCache() {
        this.gradientCanvas = document.createElement("canvas");
        this.gradientCtx = this.gradientCanvas.getContext("2d");
        this.gradientCanvas.width = 16;
        this.gradientCanvas.height = 16;

        const half = this.gradientCanvas.width / 2;
        const gradient = this.gradientCtx.createRadialGradient(
          half,
          half,
          0,
          half,
          half,
          half
        );
        gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
        gradient.addColorStop(0.3, "rgba(196, 181, 253, 0.8)");
        gradient.addColorStop(0.7, "rgba(139, 92, 246, 0.4)");
        gradient.addColorStop(1, "transparent");

        this.gradientCtx.fillStyle = gradient;
        this.gradientCtx.beginPath();
        this.gradientCtx.arc(half, half, half, 0, Math.PI * 2);
        this.gradientCtx.fill();
      }

      random(min, max) {
        if (arguments.length < 2) {
          max = min;
          min = 0;
        }
        return Math.floor(Math.random() * (max - min + 1)) + min;
      }

      randomFloat(min, max) {
        return Math.random() * (max - min) + min;
      }

      createParticle() {
        const intensityRatio = this.intensity / this.baseIntensity;
        const speedMultiplier = 1 + (intensityRatio - 1) * 1.2;
        const sizeMultiplier = 1 + (intensityRatio - 1) * 0.7;

        return {
          x:
            this.lightBarX +
            this.randomFloat(-this.lightBarWidth / 2, this.lightBarWidth / 2),
          y: this.randomFloat(0, this.h),

          vx: this.randomFloat(0.2, 1.0) * speedMultiplier,
          vy: this.randomFloat(-0.15, 0.15) * speedMultiplier,

          radius: this.randomFloat(0.4, 1) * sizeMultiplier,
          alpha: this.randomFloat(0.6, 1),
          decay: this.randomFloat(0.005, 0.025) * (2 - intensityRatio * 0.5),
          originalAlpha: 0,
          life: 1.0,
          time: 0,
          startX: 0,

          twinkleSpeed: this.randomFloat(0.02, 0.08) * speedMultiplier,
          twinkleAmount: this.randomFloat(0.1, 0.25),
        };
      }

      initParticles() {
        for (let i = 0; i < this.maxParticles; i++) {
          const particle = this.createParticle();
          particle.originalAlpha = particle.alpha;
          particle.startX = particle.x;
          this.count++;
          this.particles[this.count] = particle;
        }
      }

      updateParticle(particle) {
        particle.x += particle.vx;
        particle.y += particle.vy;
        particle.time++;

        particle.alpha =
          particle.originalAlpha * particle.life +
          Math.sin(particle.time * particle.twinkleSpeed) * particle.twinkleAmount;

        particle.life -= particle.decay;

        if (particle.x > this.w + 10 || particle.life <= 0) {
          this.resetParticle(particle);
        }
      }

      resetParticle(particle) {
        particle.x =
          this.lightBarX +
          this.randomFloat(-this.lightBarWidth / 2, this.lightBarWidth / 2);
        particle.y = this.randomFloat(0, this.h);
        particle.vx = this.randomFloat(0.2, 1.0);
        particle.vy = this.randomFloat(-0.15, 0.15);
        particle.alpha = this.randomFloat(0.6, 1);
        particle.originalAlpha = particle.alpha;
        particle.life = 1.0;
        particle.time = 0;
        particle.startX = particle.x;
      }

      drawParticle(particle) {
        if (particle.life <= 0) return;

        let fadeAlpha = 1;

        if (particle.y < this.fadeZone) {
          fadeAlpha = particle.y / this.fadeZone;
        } else if (particle.y > this.h - this.fadeZone) {
          fadeAlpha = (this.h - particle.y) / this.fadeZone;
        }

        fadeAlpha = Math.max(0, Math.min(1, fadeAlpha));

        this.ctx.globalAlpha = particle.alpha * fadeAlpha;
        this.ctx.drawImage(
          this.gradientCanvas,
          particle.x - particle.radius,
          particle.y - particle.radius,
          particle.radius * 2,
          particle.radius * 2
        );
      }

      drawLightBar() {
        const verticalGradient = this.ctx.createLinearGradient(0, 0, 0, this.h);
        verticalGradient.addColorStop(0, "rgba(255, 255, 255, 0)");
        verticalGradient.addColorStop(
          this.fadeZone / this.h,
          "rgba(255, 255, 255, 1)"
        );
        verticalGradient.addColorStop(
          1 - this.fadeZone / this.h,
          "rgba(255, 255, 255, 1)"
        );
        verticalGradient.addColorStop(1, "rgba(255, 255, 255, 0)");

        this.ctx.globalCompositeOperation = "lighter";

        const targetGlowIntensity = this.scanningActive ? 3.5 : 1;

        if (!this.currentGlowIntensity) this.currentGlowIntensity = 1;

        this.currentGlowIntensity +=
          (targetGlowIntensity - this.currentGlowIntensity) * this.transitionSpeed;

        const glowIntensity = this.currentGlowIntensity;
        const lineWidth = this.lightBarWidth;
        const glow1Alpha = this.scanningActive ? 1.0 : 0.8;
        const glow2Alpha = this.scanningActive ? 0.8 : 0.6;
        const glow3Alpha = this.scanningActive ? 0.6 : 0.4;

        const coreGradient = this.ctx.createLinearGradient(
          this.lightBarX - lineWidth / 2,
          0,
          this.lightBarX + lineWidth / 2,
          0
        );
        coreGradient.addColorStop(0, "rgba(255, 255, 255, 0)");
        coreGradient.addColorStop(
          0.3,
          `rgba(255, 255, 255, ${0.9 * glowIntensity})`
        );
        coreGradient.addColorStop(0.5, `rgba(255, 255, 255, ${1 * glowIntensity})`);
        coreGradient.addColorStop(
          0.7,
          `rgba(255, 255, 255, ${0.9 * glowIntensity})`
        );
        coreGradient.addColorStop(1, "rgba(255, 255, 255, 0)");

        this.ctx.globalAlpha = 1;
        this.ctx.fillStyle = coreGradient;

        const radius = 15;
        this.ctx.beginPath();
        this.ctx.roundRect(
          this.lightBarX - lineWidth / 2,
          0,
          lineWidth,
          this.h,
          radius
        );
        this.ctx.fill();

        const glow1Gradient = this.ctx.createLinearGradient(
          this.lightBarX - lineWidth * 2,
          0,
          this.lightBarX + lineWidth * 2,
          0
        );
        glow1Gradient.addColorStop(0, "rgba(139, 92, 246, 0)");
        glow1Gradient.addColorStop(
          0.5,
          `rgba(196, 181, 253, ${0.8 * glowIntensity})`
        );
        glow1Gradient.addColorStop(1, "rgba(139, 92, 246, 0)");

        this.ctx.globalAlpha = glow1Alpha;
        this.ctx.fillStyle = glow1Gradient;

        const glow1Radius = 25;
        this.ctx.beginPath();
        this.ctx.roundRect(
          this.lightBarX - lineWidth * 2,
          0,
          lineWidth * 4,
          this.h,
          glow1Radius
        );
        this.ctx.fill();

        const glow2Gradient = this.ctx.createLinearGradient(
          this.lightBarX - lineWidth * 4,
          0,
          this.lightBarX + lineWidth * 4,
          0
        );
        glow2Gradient.addColorStop(0, "rgba(139, 92, 246, 0)");
        glow2Gradient.addColorStop(
          0.5,
          `rgba(139, 92, 246, ${0.4 * glowIntensity})`
        );
        glow2Gradient.addColorStop(1, "rgba(139, 92, 246, 0)");

        this.ctx.globalAlpha = glow2Alpha;
        this.ctx.fillStyle = glow2Gradient;

        const glow2Radius = 35;
        this.ctx.beginPath();
        this.ctx.roundRect(
          this.lightBarX - lineWidth * 4,
          0,
          lineWidth * 8,
          this.h,
          glow2Radius
        );
        this.ctx.fill();

        if (this.scanningActive) {
          const glow3Gradient = this.ctx.createLinearGradient(
            this.lightBarX - lineWidth * 8,
            0,
            this.lightBarX + lineWidth * 8,
            0
          );
          glow3Gradient.addColorStop(0, "rgba(139, 92, 246, 0)");
          glow3Gradient.addColorStop(0.5, "rgba(139, 92, 246, 0.2)");
          glow3Gradient.addColorStop(1, "rgba(139, 92, 246, 0)");

          this.ctx.globalAlpha = glow3Alpha;
          this.ctx.fillStyle = glow3Gradient;

          const glow3Radius = 45;
          this.ctx.beginPath();
          this.ctx.roundRect(
            this.lightBarX - lineWidth * 8,
            0,
            lineWidth * 16,
            this.h,
            glow3Radius
          );
          this.ctx.fill();
        }

        this.ctx.globalCompositeOperation = "destination-in";
        this.ctx.globalAlpha = 1;
        this.ctx.fillStyle = verticalGradient;
        this.ctx.fillRect(0, 0, this.w, this.h);
      }

      render() {
        const targetIntensity = this.scanningActive
          ? this.scanTargetIntensity
          : this.baseIntensity;
        const targetMaxParticles = this.scanningActive
          ? this.scanTargetParticles
          : this.baseMaxParticles;
        const targetFadeZone = this.scanningActive
          ? this.scanTargetFadeZone
          : this.baseFadeZone;

        this.currentIntensity +=
          (targetIntensity - this.currentIntensity) * this.transitionSpeed;
        this.currentMaxParticles +=
          (targetMaxParticles - this.currentMaxParticles) * this.transitionSpeed;
        this.currentFadeZone +=
          (targetFadeZone - this.currentFadeZone) * this.transitionSpeed;

        this.intensity = this.currentIntensity;
        this.maxParticles = Math.floor(this.currentMaxParticles);
        this.fadeZone = this.currentFadeZone;

        this.ctx.globalCompositeOperation = "source-over";
        this.ctx.clearRect(0, 0, this.w, this.h);

        this.drawLightBar();

        this.ctx.globalCompositeOperation = "lighter";
        for (let i = 1; i <= this.count; i++) {
          if (this.particles[i]) {
            this.updateParticle(this.particles[i]);
            this.drawParticle(this.particles[i]);
          }
        }

        const currentIntensity = this.intensity;
        const currentMaxParticles = this.maxParticles;

        if (Math.random() < currentIntensity && this.count < currentMaxParticles) {
          const particle = this.createParticle();
          particle.originalAlpha = particle.alpha;
          particle.startX = particle.x;
          this.count++;
          this.particles[this.count] = particle;
        }

        const intensityRatio = this.intensity / this.baseIntensity;

        if (intensityRatio > 1.1 && Math.random() < (intensityRatio - 1.0) * 1.2) {
          const particle = this.createParticle();
          particle.originalAlpha = particle.alpha;
          particle.startX = particle.x;
          this.count++;
          this.particles[this.count] = particle;
        }

        if (intensityRatio > 1.3 && Math.random() < (intensityRatio - 1.3) * 1.4) {
          const particle = this.createParticle();
          particle.originalAlpha = particle.alpha;
          particle.startX = particle.x;
          this.count++;
          this.particles[this.count] = particle;
        }

        if (intensityRatio > 1.5 && Math.random() < (intensityRatio - 1.5) * 1.8) {
          const particle = this.createParticle();
          particle.originalAlpha = particle.alpha;
          particle.startX = particle.x;
          this.count++;
          this.particles[this.count] = particle;
        }

        if (intensityRatio > 2.0 && Math.random() < (intensityRatio - 2.0) * 2.0) {
          const particle = this.createParticle();
          particle.originalAlpha = particle.alpha;
          particle.startX = particle.x;
          this.count++;
          this.particles[this.count] = particle;
        }

        if (this.count > currentMaxParticles + 200) {
          const excessCount = Math.min(15, this.count - currentMaxParticles);
          for (let i = 0; i < excessCount; i++) {
            delete this.particles[this.count - i];
          }
          this.count -= excessCount;
        }
      }

      animate() {
        this.render();
        this.animationId = requestAnimationFrame(() => this.animate());
      }

      startScanning() {
        this.scanningActive = true;
        // console.log("Scanning started - intense particle mode activated");
      }

      stopScanning() {
        this.scanningActive = false;
        // console.log("Scanning stopped - normal particle mode");
      }

      setScanningActive(active) {
        this.scanningActive = active;
        // console.log("Scanning mode:", active ? "active" : "inactive");
      }

      getStats() {
        return {
          intensity: this.intensity,
          maxParticles: this.maxParticles,
          currentParticles: this.count,
          lightBarWidth: this.lightBarWidth,
          fadeZone: this.fadeZone,
          scanningActive: this.scanningActive,
          canvasWidth: this.w,
          canvasHeight: this.h,
        };
      }

      destroy() {
        if (this.animationId) {
          cancelAnimationFrame(this.animationId);
        }

        this.particles = [];
        this.count = 0;
      }
    }

    let particleScanner;

    document.addEventListener("DOMContentLoaded", () => {
      cardStream = new CardStreamController();
      particleSystem = new ParticleSystem();
      particleScanner = new ParticleScanner();

      window.setScannerScanning = (active) => {
        if (particleScanner) {
          particleScanner.setScanningActive(active);
        }
      };

      window.getScannerStats = () => {
        if (particleScanner) {
          return particleScanner.getStats();
        }
        returnnull;
      };
    });

  </script>

</html>